﻿#light

open System
open System.Collections.Generic
open System.Reflection
open TypeRepository
open AstTypes
open AstOperations
open Might.Infrastructure.ModelicaDOM
open Might.Infrastructure.ModelicaDOM.Expressions
open Might.Modelica.Annotations
open Might.Infrastructure.DOM

let isEmptyRelation(rel: Relation) = 
    match rel._arithmeticExpressionRight with
    | Some _ -> 
        false
    | None ->
        match rel._relOp with
        | None -> 
            true
        | Some _ -> 
            false

let isEmptyArithmeticExpression(arExp: ArithmeticExpression) = 
    match arExp._operatorTermList with
    | None ->        
            match arExp._unaryAddOp with
            | None ->  
                true       
            | Some _ -> 
                false        
    | Some _ ->
        false

let getSimplePrimary(exp:Expression) =     
    match exp with
    | Expression.SimpleExpressionDef simpleExp ->
        match simpleExp with
        | SimpleExpression.Single logicalExp ->
            match logicalExp._termList.Length with
            | 1 -> 
                let term = logicalExp._termList.Head
                match term._factorList.Length with
                | 1 ->
                    let factor = term._factorList.Head
                    match factor._negation with
                    | false ->
                        match isEmptyRelation(factor._relation) with
                        | true ->
                            let arExp = factor._relation._arithmeticExpressionLeft                            
                            match isEmptyArithmeticExpression(arExp) with
                            | true ->
                                let term = arExp._term
                                match term._operatorFactorList with
                                | None ->
                                    match term._factor._primaryRight with
                                    | None ->
                                        Some(term._factor._primaryLeft)
                                    | Some _ ->
                                        None
                                | Some _ -> None
                            | false ->
                                None
                        | false -> None
                    | _ -> None                    
                | _ -> None
                
            | _ -> None
        | _ -> None
    | _ -> None

let getSimpleNumberPrimary(exp:Expression) =     
    let mutable sign = None
    match exp with
    | Expression.SimpleExpressionDef simpleExp ->
        match simpleExp with
        | SimpleExpression.Single logicalExp ->
            match logicalExp._termList.Length with
            | 1 -> 
                let term = logicalExp._termList.Head
                match term._factorList.Length with
                | 1 ->
                    let factor = term._factorList.Head
                    match factor._negation with
                    | false ->
                        match isEmptyRelation(factor._relation) with
                        | true ->
                            let arExp = factor._relation._arithmeticExpressionLeft
                            sign <- arExp._unaryAddOp                           
                            match arExp._operatorTermList with
                            | None ->
                                let term = arExp._term
                                match term._operatorFactorList with
                                | None ->
                                    match term._factor._primaryRight with
                                    | None ->
                                        Some(term._factor._primaryLeft,sign)
                                    | Some _ ->
                                        None
                                | Some _ -> None
                            | Some _ ->
                                None
                        | false -> None
                    | _ -> None                    
                | _ -> None
                
            | _ -> None
        | _ -> None
    | _ -> None

let createbjectOfType(typeName:String) = 
    let annotContainerType = 
        match TypeProvider.GetRegisteredTypeBySourceName(typeName) with
        | Some registeredType -> registeredType
        | None -> raise( ArgumentException("Type " + typeName + " can not be found."))
    Activator.CreateInstance(annotContainerType)


let assignPrimitiveTypeToDefaultProp(assignedVal,expectedType:System.Type) = 
    let defaultPropNameOpt = TypeProvider.MapDefaultProperty(expectedType.Name)
    match defaultPropNameOpt with
    | Some defaultPropName ->
        let defaultProp = expectedType.GetProperty(defaultPropName)
        let obj = Activator.CreateInstance(expectedType)
        defaultProp.SetValue(obj,assignedVal,[||])
        obj
    | None -> raise(ArgumentException ("Default property missing in type " + expectedType.Name))   

let isNumberType(argType:System.Type) =
    if(argType.Equals(typeof<int>) or argType.Equals(typeof<double>) or argType.Equals(typeof<byte>)) then
        true
    else
        false

let retrievePrimitiveValue(expression:Expression,expectedType:System.Type) = 
    if(isNumberType(expectedType)) then
        match getSimpleNumberPrimary(expression) with
        | Some(Primary.IntLiteral (intLiteral),signOpt) ->
            match signOpt with
            | Some sign ->                                
                match sign with
                | AdditionOperator.Add ->
                     intLiteral :> System.Object
                | AdditionOperator.Substract ->
                    let intLiteralNegative = (0 - intLiteral)
                    intLiteralNegative :> System.Object
                | _ ->
                    raise(ArgumentException "Bad sign") 
            | None ->                            
                intLiteral :> System.Object
        | Some(Primary.FloatLiteral (floatLiteral),signOpt) ->
            match signOpt with
            | Some sign ->
                match sign with
                | AdditionOperator.Add ->
                     floatLiteral :> System.Object
                | AdditionOperator.Substract ->
                    let floatLiteralNegative = (0.0 - floatLiteral)
                    floatLiteralNegative :> System.Object
                | _ ->
                    raise(ArgumentException "Bad sign") 
            | None ->            
                floatLiteral :> System.Object
        | _ ->
            raise(ArgumentException "Invalid expression - too complex")    
    else
        match getSimplePrimary(expression) with
        | Some primary ->
            match primary with            
            | Primary.StringLiteral (strLiteral) ->
                strLiteral :> System.Object
            | Primary.BoolLiteral (boolLiteral) ->
                boolLiteral :> System.Object   
            | _ -> raise(ArgumentException "Expression does not contain primitive value")
        | None -> raise(ArgumentException "Invalid expression - not just primary")


type ExpressionListOrigin =
    | Array = 0
    | FunctionCall = 1

let insertUnknownIntoObject(filledObj:obj,unknownItems:obj list) =     
    let unknownProperty = TypeProvider.MapUnknownProperty(filledObj.GetType().Name)
    match unknownProperty with
    | None ->
        raise(Exception("Not even unknown property found, we really can't continue with mapping this annotation"))
    | Some propInfo ->                
        let mutable propVal = propInfo.GetValue(filledObj,[||])
        match propVal with
        | null ->   
            let listType = typeof<List<_>>
            let genericType = listType.GetGenericTypeDefinition().MakeGenericType([|typeof<System.Object>|])                                
            let genericListObj = Activator.CreateInstance(genericType)
            propInfo.SetValue(filledObj,genericListObj,null)
            propVal <- genericListObj
        | _ ->
            ()
        let propType = propInfo.PropertyType
        let iList = propType.GetInterface("IList",true)
        let bFlags = BindingFlags.Public ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance
        for unknownItem in unknownItems do
            let res = iList.InvokeMember("Add", bFlags,null,propVal, [|unknownItem|] )
            ()

let rec fillObjectFromExpressionList(expList: Expression list,filledObj:System.Object,filledObjType:System.Type,listOrigin:ExpressionListOrigin) = 
    let expArray = List.to_array expList
    for index = 0 to (expArray.Length - 1) do
        let mutable propertyNameOpt = 
            match listOrigin with
            | ExpressionListOrigin.Array ->
                TypeProvider.MapArrayProperty(filledObjType.Name,index)
            | ExpressionListOrigin.FunctionCall ->
                TypeProvider.MapConstructorProperty(filledObjType.Name,index)
            | _ ->
                raise(ArgumentException "Unsupported expression list origin")                
        match propertyNameOpt with
        | Some propertyName ->
            let property = filledObjType.GetProperty(propertyName)
            let propertyType = property.PropertyType
            if( propertyType.IsPrimitive = true) or (propertyType.Equals(typeof<string>)) then                                                        
                let assVal = retrievePrimitiveValue(expArray.[index],propertyType)
                property.SetValue(filledObj,assVal,[||])                        
            else
                try
                    let assignedObject = createObjectFromExpression(expArray.[index],propertyType)
                    property.SetValue(filledObj,assignedObject,[||])
                with
                | _ ->
                    insertUnknownIntoObject(filledObj,[(expArray.[index] :> System.Object)])
        | None ->
            let defaultPropertyNameOpt = TypeProvider.MapDefaultProperty(filledObjType.Name)
            match defaultPropertyNameOpt with
            | Some defaultPropertyName  ->
                let property = filledObjType.GetProperty(defaultPropertyName)
                let propertyType = property.PropertyType
                if( propertyType.IsPrimitive = true) or (propertyType.Equals(typeof<string>)) then                                                        
                    let assVal = retrievePrimitiveValue(expArray.[index],propertyType) :?> string
                    property.SetValue(filledObj,assVal,[||])                
                else
                    try
                        let assignedObject = createObjectFromExpression(expArray.[index],propertyType)
                        property.SetValue(assignedObject,filledObj,[||])
                    with
                    | _ ->
                        insertUnknownIntoObject(filledObj,[(expArray.[index] :> System.Object)])
            | None -> 
                //raise(ArgumentException("Default property in " + filledObjType.Name + " not found"))                
                insertUnknownIntoObject(filledObj,(List.map (fun a -> a :> obj) expList))

and assignNamedArguments(namedList:NamedArgument list, filledObj:System.Object,filledType:System.Type) = 
    let namedArray = List.to_array namedList
    for index = 0 to (namedArray.Length - 1) do        
        let argumentName = namedArray.[index]._argumentName
        let argumentValue = namedArray.[index]._argumentValue
        let propertyNameOpt = TypeProvider.MapProperty(filledType.Name,argumentName)
        match propertyNameOpt with
        | Some propertyName ->
            let property = filledType.GetProperty(propertyName)
            match property.PropertyType.GetInterface("IList",true) with
            | null ->
                try
                    let assignedObj = createObjectFromExpression(argumentValue,property.PropertyType)
                    property.SetValue(filledObj,assignedObj,[||])
                with
                | _ ->
                    insertUnknownIntoObject(filledObj,[(namedArray.[index] :> System.Object)])
            | _ ->
                let listElementType = property.PropertyType.GetGenericArguments().[0]
                let objList = createObjectListFromExpression( listElementType ,Some(argumentValue))
                let mutable propertyValueObject = property.GetValue(filledObj,[||])
                match propertyValueObject with
                | null ->   
                    let listType = typeof<List<_>>
                    let genericType = listType.GetGenericTypeDefinition().MakeGenericType([|listElementType|])
                    let genericListObj = Activator.CreateInstance(genericType)
                    property.SetValue(filledObj,genericListObj,null)
                    propertyValueObject <- genericListObj                                                            
                | _ ->
                    ()
                let bFlags = BindingFlags.Public ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance
                let argArray = List.to_array(objList)                 
                for arg in argArray do
                    let iList = property.PropertyType.GetInterface("IList",true)                
                    let res = iList.InvokeMember("Add",bFlags,null,propertyValueObject,[|arg|])  
                    ()              
        | None ->
            //raise(ArgumentException ("Named function call arg " + argumentName + " can not be mapped"))
            insertUnknownIntoObject(filledObj,[(namedArray.[index] :> obj)])

and createObjectFromFunctionCall(fncCall:FunctionCall) = 
    let fncName = fncCall._funcName
    let createdTypeOpt = TypeProvider.GetRegisteredTypeBySourceName(fncName)
    let obj,createdType = 
        match createdTypeOpt with
        | Some createdType ->
            Activator.CreateInstance(createdType),createdType
        | None ->
            raise(ArgumentException( "Type " + fncName + " not found"))
    match fncCall._funcArgs with
    | Some funcArgs ->
        match funcArgs with
        | FunctionArgs.PositionalAndNamed(expList,namedList) ->
            fillObjectFromExpressionList(expList,obj,createdType,ExpressionListOrigin.FunctionCall)
            assignNamedArguments(namedList,obj,createdType)                                    
        | _ -> 
            raise(ArgumentException "Unsupported function call arguments")
    | None ->
        ()
    obj
    
and createObjectFromExpression(exp:Expression, expectedType:System.Type) = 
    if(isNumberType(expectedType)) then    
        match getSimpleNumberPrimary(exp) with
        | Some(Primary.IntLiteral (intLiteral),signOpt) ->        
            let intLiteralProcessed = 
                match signOpt with
                | Some sign ->
                    match sign with
                    | AdditionOperator.Add ->
                         intLiteral
                    | AdditionOperator.Substract ->
                        let intLiteralNegative = (0 - intLiteral)
                        intLiteralNegative
                    | _ ->
                        raise(ArgumentException "Bad sign") 
                | None ->            
                    intLiteral   
            intLiteral :> System.Object
            //assignPrimitiveTypeToDefaultProp(intLiteralProcessed,expectedType)
            
        | Some(Primary.FloatLiteral (floatLiteral),signOpt) ->
            let floatLiteralProcessed = 
                match signOpt with
                | Some sign ->
                    match sign with
                    | AdditionOperator.Add ->
                         floatLiteral
                    | AdditionOperator.Substract ->
                        let floatLiteralNegative = (0.0 - floatLiteral)
                        floatLiteralNegative
                    | _ ->
                        raise(ArgumentException "Bad sign") 
                | None ->            
                    floatLiteral                    
            floatLiteral :> System.Object
            //assignPrimitiveTypeToDefaultProp(floatLiteralProcessed,expectedType)
        | _ ->
            raise(ArgumentException "Invalid expression - too complex")
    else
        match getSimplePrimary(exp) with
        | Some prim ->
            match prim with            
            | Primary.StringLiteral (strLiteral) ->
                if(expectedType.Equals(typeof<string>)) then
                    strLiteral :> System.Object
                else
                    assignPrimitiveTypeToDefaultProp(strLiteral,expectedType)
            | Primary.BoolLiteral (boolLiteral) ->
                if(expectedType.Equals(typeof<bool>)) then
                    boolLiteral :> System.Object
                else
                    assignPrimitiveTypeToDefaultProp(boolLiteral,expectedType)               
            | Primary.FunctionCallDef fncCall ->
                createObjectFromFunctionCall(fncCall)
            | Primary.ComponentReferenceDef compRef ->
                let stringRef = createStringReference(compRef)
                let lastDot = stringRef.LastIndexOf('.');
                let enumName = stringRef.Substring(0,lastDot);
                let enumValueName = stringRef.Substring(lastDot + 1 ,stringRef.Length - lastDot - 1)                
                let enumType,caseRealName = TypeProvider.MapEnumCase(enumName,enumValueName)
                Enum.Parse(enumType,caseRealName,false)
                // dorobit, asi enumy???
            | Primary.ArrayConstructionDef arrDef ->
                let obj = Activator.CreateInstance(expectedType)
                match arrDef with
                | FunctionArgs.PositionalAndNamed (expList,[]) ->
                     fillObjectFromExpressionList(expList,obj,expectedType,ExpressionListOrigin.Array)
                     obj
                | _ -> 
                   raise(ArgumentException "Invalid expression - too complex array")                                     
            | _ ->
                raise(ArgumentException ("Expression type not supported"))
        | None -> raise(ArgumentException "Invalid expression - too complex")

/// expects expression, which contains array of something and contructs object for each field of array
and createObjectListFromExpression(expectedType: System.Type,assignedVal: Expression option) =         
        // creates object of type from expression                                                               
        match assignedVal with
        | None -> []
        | Some assVal -> 
            let primOpt = getSimplePrimary(assVal)
            match primOpt with
            | Some( Primary.ArrayConstructionDef arrDef )->
                match arrDef with
                | FunctionArgs.PositionalAndNamed(expList,[]) ->
                    let createObjectFromExpressionMapping(exp) = createObjectFromExpression(exp,expectedType)
                    List.map createObjectFromExpressionMapping expList
                | _ -> raise(ArgumentException "Expecting simple array and found complex")
            | _ -> 
                raise(ArgumentException "Expecting array and found something else")

let rec fillObject(annotationObject: Object,annotation:ClassModification) =       
    // get the type of the object
    let objType = annotationObject.GetType()
    // loop through component modifications... the annotation should be built only of these
    for modif in annotation._compModif.Values do 
        try 
            // create string representation of the component reference
            let compName = createStringReference(modif._compReference)
            // get the property to which we should assign data from modification
            let mutable propertyOption = TypeProvider.MapProperty(objType.Name,compName)
            // determine whether there's default mapping
            match propertyOption with
            | None ->
                propertyOption <- TypeProvider.MapDefaultProperty(objType.Name)
            | _ -> ()            
            let propertyName =  match (propertyOption) with
                                | Some prop -> prop
                                | None -> raise( Exception("Can not map property " + compName) )
            let property = objType.GetProperty(propertyName)        
            let propType = property.PropertyType
            match propType.GetInterface("IList",true) with
            | null ->
                let assignedType = property.PropertyType
                
                let mutable assignedObj = null
                if(assignedType.IsPrimitive <> true) && (assignedType.Equals(typeof<string>) <> true) then
                    assignedObj <- Activator.CreateInstance(assignedType)                    
                
                match modif._modification with
                | None -> ()
                | Some modification ->
                    fillObject(assignedObj,modification)
                    property.SetValue(annotationObject,assignedObj,[||])
                match modif._assignedValue with 
                | None -> ()
                | Some assignedValue ->
                    assignedObj <- createObjectFromExpression(assignedValue, assignedType)
                    property.SetValue(annotationObject,assignedObj,[||])                    
            | _ ->
                let listElementType = propType.GetGenericArguments().[0]
                
                // this is just for now, in future merging assigned values and modificaiton may be added                                    
                let objList = 
                    match modif._modification with
                    | Some modif2 ->
                        let obj = 
                            match listElementType.IsAbstract with
                            | true ->
                                let elemTypeOpt = TypeProvider.GetRegisteredTypeBySourceName(compName)
                                match elemTypeOpt with
                                | Some elemType ->
                                    Activator.CreateInstance(elemType) 
                                | None ->
                                    raise(ArgumentException( "Type " + compName + " can not be mapped")) 
                            | _ -> 
                                Activator.CreateInstance(listElementType)                                                                                  
                        fillObject(obj,modif2)
                        [obj]
                    | None ->                            
                        createObjectListFromExpression( listElementType ,modif._assignedValue)
                            
                let mutable propertyValueObject = property.GetValue(annotationObject,[||])
                match propertyValueObject with
                | null ->   
                    let listType = typeof<List<_>>
                    let genericType = listType.GetGenericTypeDefinition().MakeGenericType([|listElementType|])                                
                    let genericListObj = Activator.CreateInstance(genericType)
                    property.SetValue(annotationObject,genericListObj,null)
                    propertyValueObject <- genericListObj
                | _ ->
                    ()
                let bFlags = BindingFlags.Public ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance            
                let callParams = List.to_array(objList)            
                for index = 0 to (callParams.Length - 1) do
                    let iList = propType.GetInterface("IList",true)                
                    let res = iList.InvokeMember("Add", bFlags,null,propertyValueObject, [|callParams.[index]|] )
                    ()
        with
        | e ->
            insertUnknownIntoObject(annotationObject, [(modif:> obj)])
    for clsRedef in annotation._classRedef.Values do    
        insertUnknownIntoObject(annotationObject, [(clsRedef:> obj)])
    for compRedef in annotation._compRedef.Values do
        insertUnknownIntoObject(annotationObject, [(compRedef:> obj)])

[<Mapping("Annotation")>]
type AnnotationTempHolder() = class    
    
    let _annotItems = new List<IDomNode>()    
            
    let _unknownItems = new List<obj>()
    
    [<DefaultMapping>]
    member this.AnnotItems
        with get() = _annotItems
    
    [<UnknownMapping>]    
    member this.UnknownItems
        with get() = _unknownItems
                       
end            

(*                    
let MapAnnotation(annotation:ClassModification) = 
    let annotationObject = createbjectOfType("Annotation")
    fillObject(annotationObject,annotation)
    annotationObject
 *)  

let mutable firstRun = true

let MapAnnotation(annotation:ClassModification,annotNode:IDomNode) = 
    let annotationObject = new AnnotationTempHolder()
    (*
    try
        TypeProvider.RegisterType(typeof<AnnotationTempHolder>)
    with
    | _ -> ()
    *)
    if(firstRun = true) then
        TypeProvider.RegisterType(typeof<AnnotationTempHolder>)
        firstRun <- false
    
    fillObject(annotationObject,annotation)
    
    for annotItem in annotationObject.AnnotItems do
    
        let att = annotItem.GetType().GetCustomAttributes(typeof<AnnotationDOMGroupAttribute>,false)
        match att.Length with
        | 0 -> raise( AttributeException("Class does not contain MapperAttribute"))
        | 1 -> 
            //System.Console.WriteLine("Registering type: " + regType.Name)
            let mapAtt = att.[0] :?> AnnotationDOMGroupAttribute
            annotNode.InsertChild(mapAtt.GroupName,annotItem )
        | _ -> raise( AttributeException("Too many MapperAttribute decorations."))        
    //annotationObject